package com.tuni.birthdaymoney.service.spring;

import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Map.Entry;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.mysema.query.types.expr.BooleanExpression;
import com.tuni.birthdaymoney.persistence.entity.Person;
import com.tuni.birthdaymoney.persistence.entity.QPerson;
import com.tuni.birthdaymoney.persistence.entity.QRole;
import com.tuni.birthdaymoney.persistence.entity.Role;
import com.tuni.birthdaymoney.persistence.entity.User;
import com.tuni.birthdaymoney.persistence.repository.PersonRepository;
import com.tuni.birthdaymoney.persistence.repository.RoleRepository;
import com.tuni.birthdaymoney.persistence.util.TokenGenerator;
import com.tuni.birthdaymoney.service.PersonService;
import com.tuni.birthdaymoney.service.util.email.BirthdayUtils;
import com.tuni.birthdaymoney.service.util.security.HashProvider;

@Service
public class SpringPersonService implements PersonService {

    @Autowired
    private PersonRepository personRepository;

    // @Autowired
    // private UserRepository userRepository;

    @Autowired
    private HashProvider hashProvider;

    @Autowired
    private RoleRepository roleRepository;

    @Transactional(readOnly = true)
    public Iterable<Person> getPeopleWithBirthdayAt(Calendar currentDate) {

        int day = currentDate.get(Calendar.DAY_OF_MONTH);
        int month = currentDate.get(Calendar.MONTH) + 1;

        BooleanExpression e0 = QPerson.person.fired.isFalse();
        BooleanExpression e1 = QPerson.person.wantToCelebrate.isTrue();
        BooleanExpression e2 = QPerson.person.birthday.dayOfMonth().eq(day);
        BooleanExpression e3 = QPerson.person.birthday.month().eq(month);
        BooleanExpression exp = BooleanExpression.allOf(e0, e1, e2, e3);

        return personRepository.findAll(exp);
    }

    @Transactional(readOnly = true)
    public Iterable<Person> getPeopleWithBirthdaySoon(Calendar currentDate, int periodDays) {

        BooleanExpression e0 = QPerson.person.fired.isFalse();
        BooleanExpression e1 = QPerson.person.wantToCelebrate.isTrue();
        BooleanExpression exp = BooleanExpression.allOf(e0, e1);

        Iterable<Person> temp = personRepository.findAll(exp);
        List<Person> result = new ArrayList<Person>();

        for (Person p : temp) {
            if (BirthdayUtils.isBirthdaySoon(p.getBirthday(), currentDate, periodDays)) {
                result.add(p);
            }
        }

        return result;
    }

    @Transactional(readOnly = true)
    public Iterable<Person> getAllPeople() {
        return personRepository.findAll();
    }

    @Transactional(readOnly = true)
    public Person getById(int id) {
        return personRepository.findOne(id);
    }

    @Transactional(readOnly = true)
    public Person getByAuthToken(String authToken) {
        return personRepository.findByAuthToken(authToken);
    }

    @Transactional(readOnly = true)
    public Iterable<Person> findAll(BooleanExpression expression) {
        return personRepository.findAll(expression);
    }

    @Transactional(readOnly = true)
    public Iterable<Person> findByFirstNameAndLastName(String firstName, String lastName) {
        return personRepository.findByFirstNameAndLastName(firstName, lastName);
    }

    @Transactional
    public Person addAsUser(Person person, Role role, String password) throws NoSuchAlgorithmException, InvalidKeySpecException {
        // save person first
        Person persistentPerson = add(person);
        // then create user for such person
        if (personRepository.findOne(persistentPerson.getId()).getUser() == null && password != null) {
            User user = new User();
            user.setPerson(persistentPerson);
            processPassword(user, password);
            Role persistentRole = assignRole(role);
            user.setRole(persistentRole);
            persistentPerson.setUser(user);
            return personRepository.save(persistentPerson);
        }
        return persistentPerson;
    }

    @Transactional
    public Person updateAsUser(Person person, Role role, String password) throws NoSuchAlgorithmException, InvalidKeySpecException {
        // then create user for such person
        User persistentUser = personRepository.findOne(person.getId()).getUser();
        if (persistentUser == null && password != null) {
            User user = new User();
            user.setPerson(person);
            processPassword(user, password);
            Role persistentRole = assignRole(role);
            user.setRole(persistentRole);
            person.setUser(user);
        } else if (persistentUser != null) {
            // update existing user with new roles
            Role persistentRole = assignRole(role);
            persistentUser.setRole(persistentRole);
        }
        return update(person);
    }

    private Role assignRole(Role role) {
        BooleanExpression roleFilter = QRole.role.add.eq(role.isAdd()).and(
                QRole.role.update.eq(role.isUpdate()).and(QRole.role.delete.eq(role.isDelete())));
        Role persistentRole = roleRepository.findOne(roleFilter);
        if (persistentRole == null) {
            // add new role
            // TODO: Remove not null from role description field
            role.setDescription("Unknown role");
            persistentRole = roleRepository.save(role);
        }
        return persistentRole;
    }

    private void processPassword(User user, String password) throws NoSuchAlgorithmException, InvalidKeySpecException {
        Entry<String, String> hashEntry = hashProvider.createHash(password);
        user.setSalt(hashEntry.getKey());
        user.setHashPass(hashEntry.getValue());
    }

    @Transactional(readOnly = true)
    public Iterable<Person> getNotificationPersons(Person birthdayMan) {
        BooleanExpression e0 = QPerson.person.fired.isFalse();
        BooleanExpression e1 = QPerson.person.wantToReceive.isTrue();
        BooleanExpression e2 = QPerson.person.id.ne(birthdayMan.getId());
        BooleanExpression exp = BooleanExpression.allOf(e0, e1, e2);

        return findAll(exp);
    }

    @Transactional
    public Person add(Person person) {
        person.setAuthToken(TokenGenerator.generateToken());
        return personRepository.save(person);
    }

    @Transactional
    public Person update(Person person) {
        return personRepository.save(person);
    }

    @Transactional
    public void delete(Person person) {
        personRepository.delete(person);
    }

    @Override
    public Iterable<Person> findAllOrderedByLastName(BooleanExpression expression) {
        return personRepository.findAll(expression, QPerson.person.lastName.asc());
    }
}